package cloud.xuxiaowei.core.config;

import cloud.xuxiaowei.core.annotation.ControllerAdviceAnnotation;
import cloud.xuxiaowei.core.utils.MethodParameterUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.ApplicationContext;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.JsonViewRequestBodyAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;

import java.io.IOException;
import java.lang.reflect.Type;

/**
 * {@link Controller}、{@link RestController} 请求处理
 * <p>
 * <code>org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyAdviceChain</code>
 *
 * @author xuxiaowei
 * @since 0.0.1
 * @see RequestBodyAdviceAdapter
 * @see JsonViewRequestBodyAdvice
 *
 */
@Slf4j
@ControllerAdvice
@ConditionalOnClass(RequestBodyAdviceAdapter.class)
public class RequestBodyAdviceConfig implements RequestBodyAdvice {

	private ApplicationContext applicationContext;

	@Autowired
	public void setApplicationContext(ApplicationContext applicationContext) {
		this.applicationContext = applicationContext;
	}

	@Override
	public boolean supports(MethodParameter methodParameter, Type targetType,
			Class<? extends HttpMessageConverter<?>> converterType) {

		ControllerAdviceAnnotation methodAnnotation = methodParameter
			.getMethodAnnotation(ControllerAdviceAnnotation.class);

		if (methodAnnotation == null) {
			// 未获取到注解，跳过
			return false;
		}

		String value = methodAnnotation.value();

		RequestBodyAdviceAdapter requestBodyAdviceAdapter;

		try {
			requestBodyAdviceAdapter = applicationContext.getBean(value, RequestBodyAdviceAdapter.class);
		}
		catch (NoSuchBeanDefinitionException e) {
			log.error("获取：{} 的 Bean 异常", value, e);
			return false;
		}

		boolean supports = requestBodyAdviceAdapter.supports(methodParameter, targetType, converterType);

		String methodName = MethodParameterUtils.getMethodName(methodParameter);

		log.debug("方法: {} 是否被 RequestBodyAdvice 拦截：{}", methodName, supports);

		return supports;
	}

	@Override
	public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
			Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
		// @formatter:off
		ControllerAdviceAnnotation methodAnnotation = parameter.getMethodAnnotation(ControllerAdviceAnnotation.class);
		String value = methodAnnotation.value();
		RequestBodyAdviceAdapter requestBodyAdviceAdapter = applicationContext.getBean(value, RequestBodyAdviceAdapter.class);
		// @formatter:on
		return requestBodyAdviceAdapter.beforeBodyRead(inputMessage, parameter, targetType, converterType);
	}

	@Override
	public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
			Class<? extends HttpMessageConverter<?>> converterType) {
		// @formatter:off
		ControllerAdviceAnnotation methodAnnotation = parameter.getMethodAnnotation(ControllerAdviceAnnotation.class);
		String value = methodAnnotation.value();
		RequestBodyAdviceAdapter requestBodyAdviceAdapter = applicationContext.getBean(value, RequestBodyAdviceAdapter.class);
		// @formatter:on
		return requestBodyAdviceAdapter.afterBodyRead(body, inputMessage, parameter, targetType, converterType);
	}

	@Override
	public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
			Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
		// @formatter:off
		ControllerAdviceAnnotation methodAnnotation = parameter.getMethodAnnotation(ControllerAdviceAnnotation.class);
		String value = methodAnnotation.value();
		RequestBodyAdviceAdapter requestBodyAdviceAdapter = applicationContext.getBean(value, RequestBodyAdviceAdapter.class);
		// @formatter:on
		return requestBodyAdviceAdapter.handleEmptyBody(body, inputMessage, parameter, targetType, converterType);
	}

}
